#ifndef __MY_DBMANAGER__
#define __MY_DBMANAGER__

#include "log.hpp"
#include "config.hpp"
#include "dataManager.hpp"
#include <iostream>
#include <mysql/mysql.h>
#include <vector>
#include <ctime>
#include <cstdio>
#include <cinttypes>
namespace cloudBackup
{
    // const std::string host ="localhost";
    const std::string HOST = "120.46.25.211";
    const std::string USER = "lhr";
    const std::string PASSWD = "lhr.547521";
    const std::string DB = "cloudBackup_DB";
    const unsigned int PORT = 3306;

    // 已备份的文件的属性信息  backup file Attribute information
    typedef struct BackupAInfo // 都需要管理哪些信息
    {
        uint32_t _backupId;
        uint32_t _fileId;
        uint32_t _userId;
        int _isZipped;                // 是否被压缩
        size_t _fsize;                // 文件大小
        time_t _atime;                // 最后一次访问
        time_t _ctime;                // 最后一次状态改变
        time_t _mtime;                // 最后一次修改
        std::string _backupPath;      // 备份文件存放路径./backupDir/a.txt
        std::string _zipPath;         // 压缩包存放路径 ./zipDir/a.txt.lz
        std::string _reqDownloadPath; // http_req 根据href超链接下载文件 ./download/a.txt

        BackupAInfo() {}
        BackupAInfo(uint32_t fileId, uint32_t UserId, const std::string &filePath)
        {
            NewBackupAInfo(fileId, UserId, filePath);
        }

        // BackupAInfo对象obj调用此函数 目的是设置filePath文件的属性信息
        // 传来的filePath一定是./backupDir/a.txt 因为只为备份文件添加信息
        // 而备份文件存放在./backupDir/a.txt
        bool NewBackupAInfo(uint32_t fileId, uint32_t UserId, const std::string &filePath)
        {
            // 无法为不存在的文件添加属性信息
            FileUtil fileUtil(filePath);
            if (fileUtil.Exists() == false)
            {
                Log::log(LOGERROR, "BackupInfo::NewBackupAInfo file not exists!: %s: %d", strerror(errno), errno);
                return false;
            }

            // 从配置文件读取相关信息
            Config *config = Config::GetInstance();
            std::string zipDir = config->GetZipDir();
            std::string zipSuffix = config->GetZipSuffix();
            std::string urlDownloadID = config->GetUrlDownloadID();

            this->_backupId = 0;
            this->_fileId = fileId;
            this->_userId = UserId;
            this->_isZipped = 0;
            this->_fsize = fileUtil.FileSize();
            this->_atime = fileUtil.LastATime();
            this->_ctime = fileUtil.LastCTime();
            this->_mtime = fileUtil.LastMTime();
            this->_backupPath = filePath;
            this->_zipPath = zipDir + fileUtil.FileName() + zipSuffix;    // ./zipDir/a.txt.lz
            this->_reqDownloadPath = urlDownloadID + fileUtil.FileName(); // ./download/a.txt

            return true;
        }

        void showMsg()
        {
            std::cout << "_isZipped: " << _isZipped << std::endl;
            std::cout << "_fsize: " << _fsize << std::endl;
            std::cout << "_atime: " << _atime << std::endl;
            std::cout << "_ctime: " << _ctime << std::endl;
            std::cout << "_mtime: " << _mtime << std::endl;
            std::cout << "_backupPath: " << _backupPath << std::endl;
            std::cout << "_zipPath: " << _zipPath << std::endl;
            std::cout << "_reqDownloadPath: " << _reqDownloadPath << std::endl;
        }
    } BackupAInfo;

    // -L/lib/mysql -lmysqlclient
    class DataBaseManager
    {
    private:
        MYSQL *_connect;
        bool _isConnected;
        MYSQL_RES *queryResult;
        my_ulonglong rows;
        unsigned int cols;
        MYSQL_ROW line;

    private:
        DataBaseManager()
            : _connect(nullptr), _isConnected(false), queryResult(nullptr), rows(0), cols(0), line(nullptr)
        {
            Log::log(INFO, "DataBaseManager()::mysql client Version %s ", mysql_get_client_info());
            _connect = mysql_init(NULL);
            if (_connect == NULL)
            {
                Log::log(FATAL, " %s 获取数据库句柄失败", mysql_error(_connect));
                return;
            }

            // 连接到数据库
            if (mysql_real_connect(_connect, HOST.c_str(), USER.c_str(), PASSWD.c_str(), DB.c_str(), PORT, NULL, 0) == NULL)
            {
                Log::log(FATAL, " %s 数据库连接失败", mysql_error(_connect));
                mysql_close(_connect);
                return;
            }
            mysql_set_character_set(_connect, "utf8");
            Log::log(INFO, "%s 数据库连接成功", DB.c_str());
            _isConnected = true;
        }

        ~DataBaseManager()
        {
            if (_isConnected)
            {
                mysql_free_result(queryResult);
                mysql_close(_connect);
                _isConnected = false;
            }
        }
        // 禁用拷贝构造和赋值运算符
        DataBaseManager(const DataBaseManager &) = delete;
        DataBaseManager &operator=(const DataBaseManager &) = delete;

    public:
        // 懒汉式单例 初次调用时再创建
        static DataBaseManager &getDBInstance()
        {
            Log::log(INFO, "DataBaseManager getDBInstance");
            static DataBaseManager DBInstance;
            return DBInstance;
        }

        time_t TIMESTAMP_TO_TIME_T(const char *timestampStr)
        {
            struct tm st_tm = {0}; // 初始化
            if (strptime(timestampStr, "%Y-%m-%d %H:%M:%S", &st_tm) == NULL)
            {
                std::cerr << "Error parsing timestamp: " << timestampStr << std::endl;
                return -1;
            }

            time_t t = mktime(&st_tm);
            if (t == -1)
                std::cerr << "mktime failed for timestamp: " << timestampStr << std::endl;
            return t;
        }

        bool getPasswdByUsrId(uint32_t userId, std::string &passwd)
        {
            // 保证连接状态
            if (_connect == NULL)
            {
                Log::log(FATAL, " %s 获取数据库句柄失败", mysql_error(_connect));
                return false;
            }

            // 执行查询
            char sqlBuf[256];
            snprintf(sqlBuf, 256, "select password from users  where user_id = %d", userId);
            if (mysql_query(_connect, sqlBuf))
            {
                Log::log(FATAL, " %s 查询失败", mysql_error(_connect));
                return false;
            }

            // 获取查询结果
            queryResult = mysql_store_result(_connect);
            if (nullptr == queryResult)
            {
                Log::log(FATAL, " %s 获取结果集失败\n", mysql_error(_connect));
                return false;
            }

            // 处理数据
            line = mysql_fetch_row(queryResult);
            passwd = line[0];
            return true;
        }

        // 根据备份id删除该条记录
        bool removeRecord(unsigned int backupID)
        {
            // 保证连接状态
            if (_connect == NULL)
            {
                Log::log(FATAL, " %s 获取数据库句柄失败", mysql_error(_connect));
                return false;
            }
            // 开始事务
            mysql_query(_connect, "START TRANSACTION");

            // 执行查询
            char sqlBuf[256];
            snprintf(sqlBuf, 256, "delete from backup_files where backup_id = %d", backupID);
            if (mysql_query(_connect, sqlBuf))
            {
                Log::log(FATAL, " %s 查询失败", mysql_error(_connect));
                return false;
            }

            // 提交事务
            mysql_query(_connect, "COMMIT");
            return true;
        }

        // 从 数据库ServerBackup_files表 中读取数据至内存中的哈希表
        bool loadFileAInfo(std::unordered_map<std::string, cloudBackup::BackupAInfo> &fileAInfoMap)
        {
            // 保证连接状态
            if (_connect == NULL)
            {
                Log::log(FATAL, " %s 获取数据库句柄失败", mysql_error(_connect));
                return false;
            }

            // 执行查询
            std::string sql = "select * from backup_files ";
            if (mysql_query(_connect, sql.c_str()))
            {
                Log::log(FATAL, " %s 查询失败", mysql_error(_connect));
                return false;
            }

            // 获取查询结果
            queryResult = mysql_store_result(_connect);
            if (nullptr == queryResult)
            {
                Log::log(FATAL, " %s 获取结果集失败\n", mysql_error(_connect));
                return false;
            }

            // 处理数据
            rows = mysql_num_rows(queryResult);
            cloudBackup::BackupAInfo st_fileInfo;

            for (int i = 0; i < rows; i++)
            {
                line = mysql_fetch_row(queryResult);
                st_fileInfo._backupId = atoi(line[0]);
                st_fileInfo._reqDownloadPath = line[5];

                // 数据库中有某文件属性信息 但其在服务器云端不存在 -- 不再添加至内存且删除该条记录
                FileUtil fileUtil(st_fileInfo._reqDownloadPath);
                Config *config = Config::GetInstance();
                std::string tmpZipPath = config->GetZipDir() + fileUtil.FileName() + config->GetZipSuffix();
                std::string tmpOriPath = config->GetBackupDir() + fileUtil.FileName();
                FileUtil fuZip(tmpZipPath);
                FileUtil fuOri(tmpOriPath);
                if (!fuOri.Exists() && !fuZip.Exists())
                {
                    removeRecord(st_fileInfo._backupId);
                    continue;
                }

                st_fileInfo._fileId = atoi(line[1]);
                st_fileInfo._userId = atoi(line[2]);
                st_fileInfo._zipPath = line[3];
                st_fileInfo._backupPath = line[4];
                st_fileInfo._fsize = atoi(line[6]);
                st_fileInfo._isZipped = atoi(line[7]);
                st_fileInfo._atime = TIMESTAMP_TO_TIME_T(line[8]);
                st_fileInfo._ctime = TIMESTAMP_TO_TIME_T(line[9]);
                st_fileInfo._mtime = TIMESTAMP_TO_TIME_T(line[10]);

                fileAInfoMap[st_fileInfo._reqDownloadPath] = st_fileInfo;
            }
            return true;
        }

        bool getFileIdByFEtag(uint32_t &fileId, const std::string &fileEtag)
        {
            // 保证连接状态
            if (_connect == NULL)
            {
                Log::log(FATAL, " %s 获取数据库句柄失败", mysql_error(_connect));
                return false;
            }

            // 执行查询
            char sqlBuf[256];
            snprintf(sqlBuf, 256, "select file_id from backup_records where file_etag =  '%s'", fileEtag.c_str());
            if (mysql_query(_connect, sqlBuf))
            {
                Log::log(FATAL, "通过FEtag查找FID失败: %s", mysql_error(_connect));
                return false;
            }

            // 获取查询结果
            queryResult = mysql_store_result(_connect);
            if (nullptr == queryResult)
            {
                Log::log(FATAL, " %s 获取结果集失败\n", mysql_error(_connect));
                return false;
            }

            // 处理数据
            line = mysql_fetch_row(queryResult);

            fileId = std::atoi(line[0]);
            // Log::log(INFO, "通过FEtag查找FID: %d", fileId);
            return true;
        }

        int insertRecord(cloudBackup::BackupAInfo &info)
        {
            // 保证连接状态
            if (_connect == NULL)
            {
                Log::log(FATAL, " %s 获取数据库句柄失败", mysql_error(_connect));
                return -1;
            }

            uint32_t file_id = info._fileId;
            uint32_t usr_id = info._userId;
            const char *zip_path = info._zipPath.c_str();
            const char *backup_path = info._backupPath.c_str();
            const char *reqDownload_path = info._reqDownloadPath.c_str();
            size_t file_size = info._fsize;
            int is_zip = info._isZipped;
            uint64_t file_Atime = info._atime;
            uint64_t file_Ctime = info._ctime;
            uint64_t file_Mtime = info._mtime;

            char sqlbuf[256];
            sprintf(sqlbuf, " VALUES (%u, %u, '%s', '%s', '%s', %zu, %d, FROM_UNIXTIME(%lu), FROM_UNIXTIME(%lu), FROM_UNIXTIME(%lu))",
                    file_id, usr_id, zip_path, backup_path, reqDownload_path, file_size, is_zip,
                    file_Atime, file_Ctime, file_Mtime);

            // 构建sql语句
            std::string sql;
            sql += "INSERT INTO backup_files";
            sql += " (file_id, user_id, zip_path, backup_path, req_download_path, file_size, is_zipped, fileAtime, fileCtime, fileMtime)";
            sql += sqlbuf;

            // 开始事务
            mysql_query(_connect, "START TRANSACTION");

            if (mysql_query(_connect, sql.c_str()))
            {
                Log::log(FATAL, "插入新记录失败： %s", mysql_error(_connect));
                return -1;
            }

            // 提交事务
            mysql_query(_connect, "COMMIT");

            // 备份id是主键自增 当该条记录被插入 表中有了这个备份id 此时获取到该id
            // 通过usrID和FMTime
            char sqlbuf2[256];
            sprintf(sqlbuf2, "select backup_id from backup_files where user_id = %d and fileMtime = FROM_UNIXTIME(%lu)",
                    usr_id, file_Mtime);

            // 开始事务
            mysql_query(_connect, "START TRANSACTION");

            if (mysql_query(_connect, sqlbuf2))
            {
                Log::log(FATAL, "插入新记录后获取备份ID失败: %s", mysql_error(_connect));
                return -1;
            }

            // 获取查询结果
            queryResult = mysql_store_result(_connect);
            if (nullptr == queryResult)
            {
                Log::log(FATAL, " %s 获取结果集失败\n", mysql_error(_connect));
                return -1;
            }

            // 处理数据
            line = mysql_fetch_row(queryResult);

            info._backupId = std::atoi(line[0]);

            // 提交事务
            mysql_query(_connect, "COMMIT");
            return info._backupId;
        }

        bool modifyRecord(const cloudBackup::BackupAInfo &info)
        {
            // 保证连接状态
            if (_connect == NULL)
            {
                Log::log(FATAL, " %s 获取数据库句柄失败", mysql_error(_connect));
                return false;
            }

            // 执行查询
            char sqlBuf[256];
            int is_zip = info._isZipped;
            Log::log(INFO, "文件被压缩/解压缩 zipFlag: %d backID: %d", is_zip, info._backupId);

            snprintf(sqlBuf, 256, "update backup_files set is_zipped = %d where backup_id = %d", is_zip, info._backupId);
            // 开始事务
            mysql_query(_connect, "START TRANSACTION");
            if (mysql_query(_connect, sqlBuf))
            {
                Log::log(FATAL, "修改记录时查询失败：%s", mysql_error(_connect));
                return false;
            }
            // 提交事务
            mysql_query(_connect, "COMMIT");
            return true;
        }
    };
}

#endif //__MY_DBMANAGER__
